
// Copyright (C) 2010-2011 Luca Piccioni
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//   
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;

namespace Derm.Render
{
	/// <summary>
	/// Generic OpenGL resource garbage collector.
	/// </summary>
	/// <remarks>
	/// <para>
	/// This class is used to collect <see cref="IRenderResource"/> instances for releasing resources.
	/// </para>
	/// <para>
	/// Application based on <see cref="RenderKernel"/> have automatic resource management (i.e. the resources are released
	/// automatically by the main kernel thread on disposition); this is because the main application thread automatically execute
	/// this RenderGarbageService at the appropriate time. In the case the application is not based on <see cref="RenderKernel"/>,
	/// it is an application responsability to call <see cref="DeleteGarbage"/> with appropriate arguments, otherwise application
	/// will suffer of resource leaks.
	/// </para>
	/// <para>
	/// Note that this class is not necessary in the case the application doesn't call <see cref="IRenderResource.Release"/> method
	/// (this imply also <see cref="IDisposable.Dispose"/> required by <see cref="IRenderResource"/>). In this case resource leaks
	/// are avoided by calling directly <see cref="IRenderResource.Delete"/> method with the appropriate argument.
	/// </para>
	/// </remarks>
	public sealed class RenderGarbageService : RenderKernelService
	{
		#region Constructors
		
		/// <summary>
		/// RenderGarbageService constructor.
		/// </summary>
		public RenderGarbageService() : base(ThisServiceName, ExecutionModel.Context)
		{
			
		}

		/// <summary>
		/// This service name.
		/// </summary>
		private static readonly string ThisServiceName = "Derm.Render.RenderGarbageService";

		#endregion

		#region Garbage Management

		/// <summary>
		/// Collect rendering object for associated resource deletion.
		/// </summary>
		/// <param name="garbage">
		/// A <see cref="IRenderResource"/> which need to be released. It has to be a created object.
		/// </param>
		/// <remarks>
		/// <para>
		/// This routine collect a <see cref="IRenderResource"/> instance for later resource deletion. <paramref name="garbage"/>
		/// resources will be effectively released on <see cref="DeleteGarbage"/> method call with the approriate arguments.
		/// </para>
		/// <para>
		/// Note that <paramref name="garbage"/> won't be finalized by .NET runtime garbage collector until it is effectively
		/// released.
		/// </para>
		/// </remarks>
		/// <exception cref="ArgumentNullException">
		/// Exception thrown if <paramref name="garbage"/> is null.
		/// </exception>
		internal void CollectGarbage(IRenderResource garbage)
		{
			if (garbage == null)
				throw new ArgumentNullException("garbage");

			lock (mGarbageItemsLock) {
				mGarbageItems.Add(garbage);
			}
		}

		/// <summary>
		/// Count the number of collected object for a specific object space.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> that specifies the object space.
		/// </param>
		/// <returns>
		/// It returns the number of objects collected for garbage in the object space specified by <paramref name="ctx"/>.
		/// </returns>
		public uint GetGarbageObjectCount(RenderContext ctx)
		{
			uint count = 0;

			foreach (IRenderResource obj in mGarbageItems) {
				if (obj.Exists(ctx) == true)
					count++;
			}

			return (count);
		}

		/// <summary>
		/// Delete all collected garbage.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> that would have been created (or sharing with the one) the collected
		/// objects by this service.
		/// </param>
		/// <remarks>
		/// <para>
		/// This routine calls <see cref="IRenderResource.Delete"/> for each collected <see cref="IRenderResource"/> (by using
		/// <see cref="CollectGarbage"/>).
		/// </para>
		/// </remarks>
		/// <exception cref="ArgumentNullException">
		/// Exception thrown if <paramref name="ctx"/> is null.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// Exception throw if <paramref name="ctx"/> is not current on the calling thread.
		/// </exception>
		public void DeleteGarbage(RenderContext ctx)
		{
			List<IRenderResource> rObjectDeleted = new List<IRenderResource>();

			if (ctx == null)
				throw new ArgumentNullException("ctx");
			if (ctx.IsCurrent() == false)
				throw new ArgumentException("context not current", "ctx");

			lock (mGarbageItemsLock) {
				foreach (IRenderResource obj in mGarbageItems) {
					if (obj.Exists(ctx) == true) {
						// Collect for later remotion
						rObjectDeleted.Add(obj);
						// Delete OpenGL object resources
						obj.Delete(ctx);
						// Always dispose (untrack object lifeftime)
						obj.Dispose();
					}
				}

				// Remove deleted resources
				foreach (IRenderResource obj in rObjectDeleted)
					mGarbageItems.Remove(obj);
			}
		}

		#endregion

		#region RenderKernelContextService Overrides

		/// <summary>
		/// Get the RenderGarbageService for the specified render context.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> that specify the object name space of the service.
		/// </param>
		/// <returns>
		/// It returns a RenderGarbageService which handle objects generated in the same namespace of <paramref name="ctx"/>.
		/// </returns>
		public static RenderGarbageService GetService(RenderContext ctx)
		{
			if (ctx == null)
				throw new ArgumentNullException("ctx");

			return ((RenderGarbageService)GetService(ctx.ObjectNameSpace, ThisServiceName));
		}

		/// <summary>
		/// Get the RenderGarbageService for the specified object name space.
		/// </summary>
		/// <param name="rObjectNameSpace">
		/// A <see cref="System.Guid"/> that specify the object name space of the service.
		/// </param>
		/// <returns>
		/// It returns a RenderGarbageService which handle objects generated in the name space <paramref name="rObjectNameSpace"/>.
		/// </returns>
		internal static RenderGarbageService GetService(Guid rObjectNameSpace)
		{
			return ((RenderGarbageService)GetService(rObjectNameSpace, ThisServiceName));
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="ctx">
		/// </param>
		public override void ContextExecute(RenderContext ctx)
		{
			DeleteGarbage(ctx);
		}

		/// <summary>
		/// Garbage list.
		/// </summary>
		private static List<IRenderResource> mGarbageItems = new List<IRenderResource>();

		/// <summary>
		/// Garbage list lock.
		/// </summary>
		private static object mGarbageItemsLock = new object();

		#endregion
	}
}